﻿
using System;
using System.Collections;

namespace CCompiler {
    public class CodeGenerator {
        /* local variables size */
        private int stackSize;
        /* output assembly */
        public string assemblyCode = "";
        /* three-address code instructions */
        private ArrayList instructions;

        public CodeGenerator(ArrayList insts, int stackSize) {
            instructions = insts;
            this.stackSize = stackSize;
        }

        public void EmitComment(string comment) {
            assemblyCode += ("\t; " + comment + "\r\n");
        }

        public void EmitAsmLabel(string label) {
            assemblyCode += (label + ":\r\n");
        }

        public void EmitAsmInstr(params string[] list) {
            string asm = "\t" + list[0] + "\t";
            if (list.Length > 1)
                asm += list[1];
            if (list.Length > 2)
                asm += ", " + list[2];
            if (list.Length > 3)
                asm += ", " + list[3];

            assemblyCode += asm + "\r\n";
        }

        public void LoadOperandToReg(string register, Operand operand) {
            string mov = "mov";
            if (operand is VariableOperand) {
                VariableOperand varOperand = (VariableOperand)operand;
                if (varOperand.symbol.type == Type.Char)
                    mov = "movsx";
            }
            EmitAsmInstr(mov, register, operand.GetAsmOperand(this));
        }

        public void StoreRegToMem(Operand operand, string register) {
            if (operand is VariableOperand) {
                VariableOperand varOperand = (VariableOperand)operand;
                if (varOperand.symbol.type == Type.Char)
                    register = register[1] + "l";
            }
            EmitAsmInstr("mov", operand.GetAsmOperand(this), register);
        }

        public void GenerateAsm() {
            /* assembler directives */
            EmitAsmInstr("include", "Irvine32.inc");
            EmitAsmInstr(".code");
            EmitAsmInstr("main PROC");

            /* stack initialization */
            //EmitAsmInstr("push", "ebp");
            //EmitAsmInstr("mov", "ebp", "esp");
            //EmitAsmInstr("sub", "esp", stackSize.ToString());

            EmitAsmInstr("call", "_main");
            EmitAsmInstr("exit");

            for (int i = 0; i < instructions.Count; i++) {
                if (instructions[i] is string) {
                    /* it is label */
                    EmitAsmLabel(instructions[i].ToString());
                    /* translate the next instruction */
                    continue;
                }

                InterInstruction instr = (InterInstruction)instructions[i];
                //EmitComment(instr.ToString());

                switch (instr.operation) {
                    case TokenCode.Equal:
                        if (instr.operand2 is VariableOperand) {
                            LoadOperandToReg("eax", instr.operand2);
                            //EmitAsmInstr("mov", instr.operand1.GetAsmOperand(this), "eax");
                            StoreRegToMem(instr.operand1, "eax");
                        } else
                            EmitAsmInstr("mov", instr.operand1.GetAsmOperand(this), instr.operand2.GetAsmOperand(this));
                        break;

                    case TokenCode.SubOp:
                        if (instr.operand3 == null) {
                            /* x = -y */
                            LoadOperandToReg("eax", instr.operand2);
                            EmitAsmInstr("neg", "eax");
                            StoreRegToMem(instr.operand1, "eax");
                        } else {
                            /* x = y - z */
                            LoadOperandToReg("eax", instr.operand2);
                            EmitAsmInstr("sub", "eax", instr.operand3.GetAsmOperand(this));
                            //EmitAsmInstr("mov", instr.operand1.GetAsmOperand(this), "eax");
                            StoreRegToMem(instr.operand1, "eax");
                        }
                        break;

                    case TokenCode.AddOp:
                    case TokenCode.And:
                    case TokenCode.Or:
                        string operation1 = null;
                        switch (instr.operation) {
                            case TokenCode.AddOp: operation1 = "add"; break;
                            //case TokenCode.SubOp: operation1 = "sub"; break;
                            case TokenCode.And: operation1 = "and"; break;
                            case TokenCode.Or: operation1 = "or"; break;
                        }
                        LoadOperandToReg("eax", instr.operand2);

                        EmitAsmInstr(operation1, "eax", instr.operand3.GetAsmOperand(this));

                        //EmitAsmInstr("mov", instr.operand1.GetAsmOperand(this), "eax");
                        StoreRegToMem(instr.operand1, "eax");
                        break;

                    case TokenCode.MulOp:
                        string op3;
                        //EmitAsmInstr("mov", "eax", instr.operand2.GetAsmOperand(this));
                        LoadOperandToReg("eax", instr.operand2);
                        if (instr.operand3 is NumberOperand) {
                            op3 = "ecx";
                            EmitAsmInstr("mov", "ecx", instr.operand3.GetAsmOperand(this));
                        } else
                            op3 = instr.operand3.GetAsmOperand(this);

                        EmitAsmInstr("imul", op3);

                        //EmitAsmInstr("mov", instr.operand1.GetAsmOperand(this), "eax");
                        StoreRegToMem(instr.operand1, "eax");
                        break;

                    case TokenCode.DivOp:
                    case TokenCode.ModOp:
                        LoadOperandToReg("eax", instr.operand2);
                        EmitAsmInstr("cdq"); // extend eax sign to edx

                        if (instr.operand3 is NumberOperand) {
                            /* immidiate operands is not allowed */
                            EmitAsmInstr("mov", "ecx", instr.operand3.GetAsmOperand(this));
                            EmitAsmInstr("idiv", "ecx");
                        } else
                            EmitAsmInstr("idiv", instr.operand3.GetAsmOperand(this));

                        if (instr.operation == TokenCode.DivOp)
                            /* return the quotient*/
                            StoreRegToMem(instr.operand1, "eax");
                        else
                            /* return the remainder */
                            StoreRegToMem(instr.operand1, "edx");
                        break;

                    case TokenCode.Not:
                        EmitAsmInstr("xor", "eax", "eax");
                        LoadOperandToReg("edx", instr.operand2);
                        EmitAsmInstr("cmp", "edx", "0");
                        EmitAsmInstr("sete", "al");
                        StoreRegToMem(instr.operand1, "eax");
                        break;

                    case TokenCode.Less:
                    case TokenCode.Greater:
                    case TokenCode.LessOrEq:
                    case TokenCode.GreaterOrEq:
                    case TokenCode.EqualEqual:
                    case TokenCode.NotEq:
                        string operation2 = null;
                        switch (instr.operation) {
                            case TokenCode.Less: operation2 = "setl"; break;
                            case TokenCode.Greater: operation2 = "setg"; break;
                            case TokenCode.LessOrEq: operation2 = "setle"; break;
                            case TokenCode.GreaterOrEq: operation2 = "setge"; break;
                            case TokenCode.EqualEqual: operation2 = "sete"; break;
                            case TokenCode.NotEq: operation2 = "setne"; break;
                        }
                        EmitAsmInstr("xor", "edx", "edx");
                        LoadOperandToReg("eax", instr.operand2);
                        LoadOperandToReg("ecx", instr.operand3);
                        EmitAsmInstr("cmp", "eax", "ecx");
                        EmitAsmInstr(operation2, "dl");

                        //EmitAsmInstr("mov", instr.operand1.GetAsmOperand(this), "edx");
                        StoreRegToMem(instr.operand1, "edx");
                        break;

                    case TokenCode.If:
                        LoadOperandToReg("eax", instr.operand1);
                        EmitAsmInstr("cmp", "eax", "0");
                        EmitAsmInstr("jne", instr.operand2.GetAsmOperand(this));
                        break;

                    case TokenCode.Goto:
                        EmitAsmInstr("jmp", instr.operand1.GetAsmOperand(this));
                        break;

                    case TokenCode.PrintInt:
                        LoadOperandToReg("eax", instr.operand1);
                        EmitAsmInstr("call", "WriteInt");
                        break;

                    case TokenCode.PrintStr:
                        EmitAsmInstr("lea", "edx", instr.operand1.GetAsmOperand(this));
                        EmitAsmInstr("call", "WriteString");
                        break;

                    case TokenCode.PrintChar:
                        //EmitAsmInstr("xor", "eax", "eax");
                        EmitAsmInstr("mov", "al", instr.operand1.GetAsmOperand(this));
                        EmitAsmInstr("call", "WriteChar");
                        break;

                    case TokenCode.ReadStr:
                        EmitAsmInstr("lea", "edx", instr.operand1.GetAsmOperand(this));
                        LoadOperandToReg("ecx", instr.operand2);
                        EmitAsmInstr("call", "ReadString");
                        break;

                    case TokenCode.ReadInt:
                        EmitAsmInstr("call", "ReadInt");
                        StoreRegToMem(instr.operand1, "eax");
                        break;

                    case TokenCode.DecStackPtr:
                        EmitAsmInstr("sub", "esp", instr.operand1.GetAsmOperand(this));
                        break;

                    case TokenCode.IncStackPtr:
                        EmitAsmInstr("add", "esp", instr.operand1.GetAsmOperand(this));
                        break;

                    case TokenCode.Enter:
                        EmitAsmInstr("enter", instr.operand1.GetAsmOperand(this), "0");
                        break;

                    case TokenCode.Param:
                        VariableOperand varOp = instr.operand1 as VariableOperand;
                        if ((varOp != null) && (varOp.symbol.type == Type.Char)) {
                            LoadOperandToReg("eax", varOp);
                            EmitAsmInstr("push", "eax");
                        } else {
                            EmitAsmInstr("push", instr.operand1.GetAsmOperand(this));
                        }
                        break;

                    case TokenCode.Call:
                        EmitAsmInstr("call", instr.operand2.GetAsmOperand(this));
                        StoreRegToMem(instr.operand1, "eax");
                        break;

                    case TokenCode.Return:
                        if (instr.operand1 != null)
                            LoadOperandToReg("eax", instr.operand1);

                        EmitAsmInstr("leave");
                        EmitAsmInstr("ret");
                        break;
                }
            }
            //EmitAsmInstr("exit");
            EmitAsmInstr("main ENDP");
            EmitAsmInstr("End main");
        }
    }
}